import datetime
import json
import os
import sys
import math
import struct
import platform
import psycopg2
import xmltodict
import talib as ta
import pandas as pd
from pandas import Timedelta
from pytdx.hq import TdxHq_API
from collections import namedtuple
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc
import xml.etree.ElementTree as ET
import dash
import threading
import logging


pd.set_option('display.max_columns', None)      #显示所有列
pd.set_option('expand_frame_repr', False)		# 显示宽度
#显示所有行
#pd.set_option('display.max_rows', None)

class QDashPlotly(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.dash = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.CYBORG])

    def runThread(self, **kwargs):
        threading.Thread(target=self.dash.run_server, kwargs=kwargs, daemon=True).start()

    def runing(self):
        self.dash.run(debug=True)

class QRouter(object):
    # 信号构建器：每个参与路由的窗口类都必须继承该类。
    class RouteSignal(object):
        signal = pyqtSignal(type, tuple)

    def __init__(self, home, data) -> None:
        super(QRouter, self).__init__()
        self.handle(home, data)

    # 路由处理器
    def handle(self, clazz, data) -> None:
        clazz_instance = clazz(data)
        clazz_instance.show()
        clazz_instance.signal.connect(lambda to_clazz, data: (clazz_instance.close(), self.handle(to_clazz, data)))


class CustomError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

class TdxSecurity():
    def __init__(self, param=None):
        units = QUnits()
        # TDX_DIR 为通达信根目录
        if(param is None):
            self.TdxPath = os.path.dirname(units.XML2ObjFromFile('./setting.xml').setting.tdx.path)
        else:
            self.TdxPath = param

    def resolve(self, *args, **kwargs):
        return os.path.join(self.TdxPath, *args, **kwargs)

    # 获取股票及时行情
    def getStockFromApi(self, stocks, isDp=False):
        try:
            api = TdxHq_API(multithread=True)  # 创建对象
            with api.connect('119.147.212.81', 7709):
                if (isDp):
                    return api.to_df(api.get_security_quotes(stocks))
                else:
                    return api.get_security_quotes(stocks)
        except Exception as e:
            logging.warning(e)

    # 获取股票k线数据
    '''
    * category-> K线种类
        0 5分钟K线 
        1 15分钟K线 
        2 30分钟K线 
        3 1小时K线 
        4 日K线
        5 周K线
        6 月K线
        7 1分钟
        8 1分钟K线 9 日K线
        10 季K线
        11 年K线
    * market -> 市场代码 0:深圳，1:上海
    * stockcode -> 证券代码;
    * start -> 指定的范围开始位置;
    * count -> 用户要请求的 K 线数目，最大值为 800
    '''

    def getStockBarsFromApi(self, category, market, stockcode, start, count, isDp=False):
        api = TdxHq_API(multithread=True)  # 创建对象
        with api.connect('119.147.212.81', 7709):
            if (isDp):
                data = api.to_df(api.get_security_bars(category, market, stockcode, start, count))
                df_data = pd.DataFrame()
                df_data = df_data.assign(datetime=data['datetime'])
                df_data = df_data.assign(date=data.apply(lambda x: datetime.date(x['year'], x['month'], x['day']), axis=1))
                df_data = df_data.assign(time=data.apply(lambda x: datetime.time(x['hour'], x['minute']), axis=1))
                df_data = df_data.assign(open=data['open'])
                df_data = df_data.assign(high=data['high'])
                df_data = df_data.assign(low=data['low'])
                df_data = df_data.assign(close=data['close'])
                df_data = df_data.assign(ratio=data['close'].diff())
                df_data = df_data.assign(amount=data['amount'])
                df_data = df_data.assign(volume=data['vol'])
                df_data = df_data.assign(openinterest=-1)
                df_data = df_data.dropna()  # 去除空值
                df_data.index = pd.to_datetime(df_data.datetime)
                return df_data
            else:
                return api.get_security_bars(category, market, stockcode, start, count)

    # 获取股票指数数据
    '''
    * category-> K线种类
        0 5分钟K线 
        1 15分钟K线 
        2 30分钟K线 
        3 1小时K线 
        4 日K线
        5 周K线
        6 月K线
        7 1分钟
        8 1分钟K线 
        9 日K线
        10 季K线
        11 年K线
    * market -> 市场代码 0:深圳，1:上海
    * stockcode -> 证券代码;
    * start -> 指定的范围开始位置;
    * count -> 用户要请求的 K 线数目，最大值为 800
    '''

    def getIndexBarsFromApi(self, category, market, stockcode, start, count, isDp=False):
        try:
            api = TdxHq_API(multithread=True)  # 创建对象
            with api.connect('119.147.212.81', 7709):
                if (isDp):
                    return api.to_df(api.get_index_bars(category, market, stockcode, start, count))
                else:
                    return api.get_index_bars(category, market, stockcode, start, count)
        except Exception as e:
            logging.warning(e)

    # 根据通达信5分钟周期数据，生成其他周期数据
    def lc5Resample(self, data_lc5, rule):
        '''if rule == '60T':
            data_lc5 = self._change_13_11_14_12(data_lc5)'''
        # 重新采样Open列数据
        df_time = data_lc5['time'].resample(rule=rule, closed='right', label='left').last()
        df_open = data_lc5['open'].resample(rule=rule, closed='right', label='left').first()
        df_high = data_lc5['high'].resample(rule=rule, closed='right', label='left').max()
        df_low = data_lc5['low'].resample(rule=rule, closed='right', label='left').min()
        df_close = data_lc5['close'].resample(rule=rule, closed='right', label='left').last()
        df_volume = data_lc5['volume'].resample(rule=rule, closed='right', label='left').sum()
        df_amount = data_lc5['amount'].resample(rule=rule, closed='right', label='left').sum()
        df_openinterest = data_lc5['openinterest'].resample(rule=rule, closed='right', label='left').sum()
        # 生成新周期数据
        df_data = pd.DataFrame()
        df_data = df_data.assign(datetime=data_lc5['datetime'])
        df_data = df_data.assign(date=data_lc5['date'].dt.strftime('%Y-%m-%d'))
        df_data = df_data.assign(time=df_time)
        df_data = df_data.assign(open=df_open)
        df_data = df_data.assign(high=df_high)
        df_data = df_data.assign(low=df_low)
        df_data = df_data.assign(close=df_close)
        df_data = df_data.assign(ratio=df_close.diff())
        df_data = df_data.assign(amount=df_amount)
        df_data = df_data.assign(volume=df_volume)
        df_data = df_data.assign(openinterest=df_openinterest)
        df_data = df_data.dropna()                  # 去除空值
        return df_data

    def _change_13_11_14_12(self, df) -> pd.DataFrame:
        date = []
        for i in df.index:
            if i.hour == 13:
                i = i - Timedelta('02:00:00')
            if i.hour == 14 and i.minute == 0 and i.second == 0:
                i = i - Timedelta('02:00:00')
            date.append(i)
        df = df.assign(date=pd.Series(date, index=df.index))
        df.set_index(['date'], inplace=True)
        return df

    # 获取本地K线数据
    def getStockBarsFromLocal(self, ts_code, cycle=None):
        if (cycle in ('1h', '30m', '15m', '5m')):
            filename = ts_code.split(".")[1] + ts_code.split(".")[0] + ".lc5"
            filepath = self.resolve("vipdoc", ts_code.split(".")[1], "fzline", filename)
            pack = 'HHffffllf'
        elif (cycle == '1m'):
            filename = ts_code.split(".")[1] + ts_code.split(".")[0] + ".lc1"
            filepath = self.resolve("vipdoc", ts_code.split(".")[1], "minline", filename)
            pack = 'HHffffllf'
        else:
            filename = ts_code.split(".")[1] + ts_code.split(".")[0] + ".day"
            filepath = self.resolve("vipdoc", ts_code.split(".")[1], "lday", filename)
            pack = 'lllllfll'
        kline = self._read_kline(filepath, pack)
        #kline["ts_code"] = ts_code
        kline.index = pd.to_datetime(kline["datetime"])
        kline.index.name = "index"
        #kline = kline.rename(columns={"vol": "volume"})
        usecols = ("datetime date time open high low close ratio amount volume openinterest".split())
        return kline[usecols]

    # 根据二进制前两段拿到日期分时
    def _parseDateTime(self, h1, h2) -> str:  # H1->0,1字节; H2->2,3字节;
        return self._parseDate(h1) + " " + self._parseTime(h2)

    def _formatDate(self, year, m, d):
        if (m < 10):
            month = '0' + str(m)
        else:
            month = str(m)
        if (d < 10):
            day = '0' + str(d)
        else:
            day = str(d)
        return str(year) + "-" + month + "-" + day

    def _parseDate(self, h):
        year = math.floor(h / 2048) + 2004  # 解析出年
        month = math.floor(h % 2048 / 100)  # 月
        day = h % 2048 % 100  # 日
        return self._formatDate(year, month, day)

    def _parseTime(self, h):
        hour = math.floor(h / 60)  # 小时
        minute = h % 60  # 分钟
        if hour < 10:  # 如果小时小于两位, 补0
            hour = "0" + str(hour)
        if minute < 10:  # 如果分钟小于两位, 补0
            minute = "0" + str(minute)
        return str(hour) + ":" + str(minute)

    # 读取K线源文件
    def _read_kline(self, filepath, pack='lllllfll'):
        with open(filepath, "rb") as f:
            usecols = "datetime date time open high low close ratio amount volume openinterest".split()
            buffers = []
            i = 0
            while True:
                buffer = f.read(32)
                if not buffer:
                    break
                buffer = struct.unpack(pack, buffer)
                if i == 0:
                    preClose = buffer[4] / 100
                ratio = round((buffer[4] / 100 - preClose) / preClose * 100, 3)
                preClose = buffer[4] / 100
                i = i + 1
                if (pack == 'lllllfll'):
                    year = int(buffer[0] / 10000)
                    month = int((buffer[0] % 10000) / 100)
                    day = (buffer[0] % 10000) % 100
                    date = self._formatDate(year, month, day)
                    buffers.append((date + ' 15:00', date, '15:00', buffer[1]/100, buffer[2]/100, buffer[3]/100, buffer[4]/100, ratio, buffer[5], buffer[6], buffer[7]))
                else:
                    buffers.append((self._parseDateTime(buffer[0], buffer[1]), self._parseDate(buffer[0]), self._parseTime(buffer[1]),
                                    buffer[2], buffer[3], buffer[4], buffer[5], ratio, buffer[6], buffer[7], buffer[8]))
            kline = pd.DataFrame(buffers, columns=usecols)
        kline["datetime"] = kline["datetime"].astype(str)
        price_columns = ["open", "high", "low", "close"]
        kline[price_columns] = kline[price_columns].apply(lambda x: x)
        return kline

class StockAnalysis:
    def __init__(self):
        self.units = QUnits()
        self.setting = self.units.XML2ObjFromFile('./setting.xml').setting

    def getStockData(self, code_ids, category):
        conn = self.units.dbConnect(self.setting.pgsql)
        stockSecurityData = dict()
        try:
            cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
            cursor.execute("SELECT * FROM trading.stock_view  WHERE stock_code IN %s" % str(tuple(code_ids)))
            for row in cursor.fetchall():
                stockSecurityData[row['stock_code']] = dict()
                stockSecurityData[row['stock_code']]['info'] = row
                stockSecurityData[row['stock_code']]['data'] = self.units.getStockSecurityBars(category, row['market'], row['stock_code'], 1, 300, True)
                stockSecurityData[row['stock_code']]['data'].index = pd.to_datetime(stockSecurityData[row['stock_code']]['data'].datetime)
                stockSecurityData[row['stock_code']]['data']['volume'] = stockSecurityData[row['stock_code']]['data'].vol
                stockSecurityData[row['stock_code']]['data']['openinterest'] = 0
        except CustomError as e:
            logging.warning(str(e.value))
        except Exception as e:
            logging.warning(e)
        finally:
            self.units.dbDisconnect(conn)
            return stockSecurityData

    '''
    布林带线交易策略，根据股价与布林带上轨和布林带下轨之间的关系确定多空，在价格上穿或下穿布林带线上下轨时产生交易信号。布林带线的均线类型不可选
    1，当价格上穿上轨时，产生全仓买入信号
    2，当价格下穿下轨时，产生全仓卖出信号
    '''
    def bollinger(self, data, period=20):
        df = pd.DataFrame()
        df['bollLower'], df['bollMiddle'], df['bollUpper'] = ta.BBANDS(data.close, timeperiod=period, nbdevup=2, nbdevdn=2, matype=0)
        return df

    def ema(self, data, short=13, middle=34, long=55):
        df = pd.DataFrame()
        ta.set_compatibility(1)
        df['emaS'] = ta.EMA(data.close, short)
        df['emaM'] = ta.EMA(data.close, middle)
        df['emaL'] = ta.EMA(data.close, long)
        return df

    '''
    Volume：成交量
    '''
    def volume(self, data, short=13, long=89):
        df = pd.DataFrame()
        df['volShort'] = ta.MA(data.volume, timeperiod=short, matype=0)
        df['volLong'] = ta.MA(data.volume, timeperiod=long, matype=0)
        return df

    '''
    MACD：移动平滑异同平均线
    应用：
        1.DIF 与MACD均为正值亦即在中轴线上，大势属多头市场，DIF 向上突破 MACD，应作买。若 DIF向下跌破MACD应只可作回档，暂时获利了解。
        2.反之DIF与MACD均为负值时，即在0轴线以下时，大势属空头市场，DIF 向下跌破MACD，可作卖。若 DIF向上突破MACD只可作空头暂时补空。
        3.如同强弱指标，背离走势也适用在 MACD的图形上，当MACD图形与 K线 图趋势线发生背离时亦为反转讯号。
        4.MACD无法预知高价及低价。盘局时，失误率较高, 但如配合 RSI及KD线 应用则可以解决此二缺点.
        5.运用柱形图的变化可提早作买或作卖，免得失去一段行情，但注意有时 亦会因贪小而失大。
    从MACD的走势，投资者可以发现三种讯息： 
        1. 当MACD升穿Signal Line，入市讯息；
        2. 当MACD跌穿Signal Line，出市讯息。 
        3. 当MACD上升时，股票价格可能是超买
    交易策略:
        1.DIFF、DEA均为正，DIFF向上突破DEA，买入信号。       
        2.DIFF、DEA均为负，DIFF向下跌破DEA，卖出信号。       
        3.DEA线与K线发生背离，行情反转信号。       
        4.分析MACD柱状线，由正变负，卖出信号；由负变正，买入信号。
    '''
    def macd(self, data, short=12, long=6, signal=9):
        df = pd.DataFrame()
        df['diff'], df['dea'], df['macd'] = ta.MACD(data.close, short, long, signal)
        return df

    '''
    RSI：相对强度指数 
    交易策略：
    RSI 指标度量最近价格变化的幅度，从而判断目前股票属于超卖还是超买状态
    1, 当RSI > ulim时，设置持仓目标为1
    2, 当RSI < llim时，设置持仓目标为-1
    3, 其余情况设置持仓目标为0
    '''
    def rsi(self, data, period=14):
        df = pd.DataFrame()
        df['rsi'] = ta.RSI(data.close, period)
        return df

    '''
    CCI：商品渠道指数
    交易策略：
    CCI商品渠道指数被用来判断当前股价位于超卖还是超买区间，本策略使用这个指标生成投资仓位目标
    1, 当CCI大于0时，输出弱多头
    2, 当CCI小于0时，输出弱空头
    3, 当CCI大于50时，输出强多头
    4, 当CCI小于 - 50时，输出强空头
    '''
    def cci(self, data, period=14):
        df = pd.DataFrame()
        df['cci'] = ta.CCI(data.high, data.low, data.close, period)
        return df

    '''
    KDJ：随机指数 
    STOCH 交易策略：
    STOCH 指标度量价格变化的动量，并且动量的大小判断价格趋势，并生成比例买卖交易信号。
    1, 当k > 80时，产生逐步卖出信号，每周期卖出持有份额的30%
    2, 当k < 20时，产生逐步买入信号，每周期买入总投资额的10%
    '''
    def kdj(self, data, fastk_period=9, slowk_period=3, slowd_period=3):
        df = pd.DataFrame()
        df['K'], df['D'] = ta.STOCH(data.high, data.low, data.close, fastk_period=fastk_period, slowk_period=slowk_period, slowk_matype=1, slowd_period=slowd_period, slowd_matype=1)  # 计算kdj的正确配置
        df.loc[:, 'J'] = 3.0 * df.loc[:, 'K'] - 2.0 * df.loc[:, 'D']
        return df

    '''
    SKD：快速随机指标 
    STOCHF 交易策略：
    STOCHF 指标度量价格变化的动量，与STOCH策略类似，使用快速随机指标判断价格趋势，并生成比例买卖交易信号。
    1, 当k > 80时，产生逐步卖出信号，每周期卖出持有份额的30%
    2, 当k < 20时，产生逐步买入信号，每周期买入总投资额的10%    
    '''
    def skd(self, data, fastk_period=9, fastd_period=3):
        df = pd.DataFrame()
        df['fastk'], df['fastd'] = ta.STOCHF(data.high, data.low, data.close, fastk_period=fastk_period, fastd_period=fastd_period, fastd_matype=0)
        return df

    '''
    MFI：货币流向指数
    交易策略：
    MFI指数用于判断股价属于超买还是超卖状态，本策略使用MFI指标生成交易信号
    1, 当 MFI > 20 时，持续不断产生10 % 买入交易信号
    2, 当 MFI > 80 时，持续不断产生30 % 卖出交易信号，持续卖出持仓股票
    '''
    def mfi(self, data):
        df = pd.DataFrame()
        df['mfi'] = ta.MFI(data.high, data.low, data.close, data.volume)
        return df

    def obv(self, data):
        df = pd.DataFrame()
        obvta = ta.OBV(data['close'], data['volume'])
        obv = []
        for i in range(0, len(data)):
            if i == 0:
                obv.append(data['volume'].values[i])
            else:
                if data['close'].values[i] > data['close'].values[i - 1]:
                    obv.append(obvta[i - 1] + data['volume'].values[i])
                if data['close'].values[i] < data['close'].values[i - 1]:
                    obv.append(obvta[i - 1] - data['volume'].values[i])
                if data['close'].values[i] == data['close'].values[i - 1]:
                    obv.append(obvta[i - 1])
        df['obv'] = obv
        return df

    def atr(self, data, period=14, multiplier=1.5):
        df = pd.DataFrame()
        df['atr'] = ta.ATR(data.high, data.low, data.close, timeperiod=period)
        df['trange'] = ta.TRANGE(data.high, data.low, data.close)
        df['tr'] = ta.WMA(df['trange'], period)
        '''upper = []
        lower = []
        for i in range(0, len(data)):
            upper.append(df['tr'][i] * multiplier + data.high)
            lower.append(data.low - df['tr'][i] * multiplier)
        df['artUpper'] = upper
        df['artLower'] = lower'''
        return df

    '''
    WR：威廉指标
    WILLR 交易策略：
    WILLR 指标被用于计算股价当前处于超买还是超卖区间，并用于生成交易信号
    1, 当 WILLR > -l 时，产生逐步卖出信号，每周期卖出持有份额的30%
    2, 当 WILLR < -u 时，产生逐步买入信号，每周期买入总投资额的10%
    '''
    def willr(self, data, long=10, short=6):
        df = pd.DataFrame()
        df['willrL'] = ta.WILLR(data.high, data.low, data.close, timeperiod=long)
        df['willrS'] = ta.WILLR(data.high, data.low, data.close, timeperiod=short)
        return df

    # SAR：抛物线指标
    # acceleration：加速因子；maximum：极点价
    def sar(self, data, acceleration=2, maximum=2):
        df = pd.DataFrame()
        df['sar'] = ta.SAR(data.high, data.low, acceleration, maximum)
        df['sarext'] = ta.SAREXT(data.high, data.low)
        return df

    def cross(self, data, key1, key2, i=0):
        if(type(key1) == str):
            val1B = data.iloc[i-2][key1]
            val1A = data.iloc[i-1][key1]
        elif(type(key1) == int):
            val1B = val1A = key1
        else:
            val1B = val1A = 0
        if(type(key2) == str):
            val2B = data.iloc[i-2][key2]
            val2A = data.iloc[i-1][key2]
        elif(type(key2) == int):
            val2B = val2A = key2
        else:
            val2A = val2B = 0
        if(data.iloc[i-2][key1] < val2B and data.iloc[i-1][key1] > val2A):
            return True
        else:
            return False

class QUnits(object):
    def toastNotifier(self, title, message):
        platform = self.checkPlatform()
        if(platform == 'Windows'):
            from win10toast import ToastNotifier
            toaster = ToastNotifier()
            toaster.show_toast(title=title, msg=message, icon_path="f.ico", duration=10, threaded=True)
        elif (platform == 'MacOS'):
            from subprocess import call
            cmd = 'display notification \"' + message + '\" with title \"' + title + '\"' + ' sound name \"Frog\"'
            call(["osascript", "-e", cmd])
        else:
            pass

    def checkPlatform(self):
        if sys.platform.startswith('win'):
            return 'Windows'
        elif sys.platform.startswith('darwin'):
            return 'MacOS'
        elif sys.platform.startswith('linux'):
            if 'Microsoft' in platform.release(): \
                return 'Windows Subsystem for Linux (WSL)'
            else:
                return 'Linux'
        else:
            return 'Unknown'
    def dbConnect(self, pgsql):
        return psycopg2.connect(
            database=pgsql.name,
            user=pgsql.user,
            password=pgsql.password,
            host=pgsql.address,
            port=pgsql.port
        )

    def dbDisconnect(self, conn):
        conn.commit()
        conn.close()

    def dict2obj(self, object):
        if isinstance(object, dict):
            return namedtuple(
                'struct', object.keys()
            )(
                *(self.dict2obj(val) for val in object.values())
            )
        elif isinstance(object, (tuple, list, set, frozenset)):
            return type(object)(self.dict2obj(_) for _ in object)
        else:
            return object

    def getKeys(self, data):
        keysAll_list = []
        def getkeys(data):  # 遍历json所有key
            if (type(data) == type({})):
                keys = data.keys()
                for key in keys:
                    value = data.get(key)
                    if (type(value) != type({}) and type(value) != type([])):
                        keysAll_list.append(key)
                    elif (type(value) == type({})):
                        keysAll_list.append(key)
                        getkeys(value)
                    elif (type(value) == type([])):
                        keysAll_list.append(key)
                        for para in value:
                            if (type(para) == type({}) or type(para) == type([])):
                                getkeys(para)
                            else:
                                keysAll_list.append(para)
        getkeys(data)
        return keysAll_list

    # 检测目标字段tagkey是否在data(json数据)中
    def isExtend(self, data, tagkey):
        if (type(data) != type({})):
            print('please input a json!')
        else:
            key_list = self.getKeys(data)
            for key in key_list:
                if (key == tagkey):
                    return True
        return False

    def readJsonFile(self, jsonFile):
        data = False
        if os.path.isfile(jsonFile):
            with open(jsonFile, 'r', encoding='utf-8') as f:
                if os.path.getsize(jsonFile) != 0:
                    data = self.dict2obj(json.load(f))
        return data

    def writeJsonFile(self, jsonFile, jsonData):
        with open(jsonFile, 'w', encoding='utf-8') as f:
            json.dump(jsonData, f, ensure_ascii=False)
        return jsonData

    # 获取当前时间
    def showCurrentTime(self, timeLabel):
        # 获取系统当前时间
        time = QtCore.QDateTime.currentDateTime()
        # 设置系统时间的显示格式
        timeDisplay = time.toString('yyyy-MM-dd dddd hh:mm:ss')
        # print(timeDisplay)
        # 状态栏显示
        timeLabel.setText(timeDisplay)

    # 获取股票及时行情
    def getStockSecurity(self, stocks, isDp=False):
        try:
            api = TdxHq_API(multithread=True)  # 创建对象
            with api.connect('119.147.212.81', 7709):
                if (isDp):
                    return api.to_df(api.get_security_quotes(stocks))
                else:
                    return api.get_security_quotes(stocks)
        except Exception as e:
            logging.warning(e)

    # 获取股票k线数据
    '''
    * category-> K线种类
        0 5分钟K线 
        1 15分钟K线 
        2 30分钟K线 
        3 1小时K线 
        4 日K线
        5 周K线
        6 月K线
        7 1分钟
        8 1分钟K线 9 日K线
        10 季K线
        11 年K线
    * market -> 市场代码 0:深圳，1:上海
    * stockcode -> 证券代码;
    * start -> 指定的范围开始位置;
    * count -> 用户要请求的 K 线数目，最大值为 800
    '''
    def getStockSecurityBars(self, category, market, stockcode, start, count, isDp=False):
        try:
            api = TdxHq_API(multithread=True)  # 创建对象
            with api.connect('119.147.212.81', 7709):
                if (isDp):
                    return api.to_df(api.get_security_bars(category, market, stockcode, start, count))
                else:
                    return api.get_security_bars(category, market, stockcode, start, count)
        except Exception as e:
            logging.warning(e)

    # 获取股票指数数据
    '''
    * category-> K线种类
        0 5分钟K线 
        1 15分钟K线 
        2 30分钟K线 
        3 1小时K线 
        4 日K线
        5 周K线
        6 月K线
        7 1分钟
        8 1分钟K线 
        9 日K线
        10 季K线
        11 年K线
    * market -> 市场代码 0:深圳，1:上海
    * stockcode -> 证券代码;
    * start -> 指定的范围开始位置;
    * count -> 用户要请求的 K 线数目，最大值为 800
    '''
    def getStockIndexBars(self, category, market, stockcode, start, count, isDp=False):
        try:
            api = TdxHq_API(multithread=True)  # 创建对象
            with api.connect('119.147.212.81', 7709):
                if (isDp):
                    return api.to_df(api.get_index_bars(category, market, stockcode, start, count))
                else:
                    return api.get_index_bars(category, market, stockcode, start, count)
        except Exception as e:
            logging.warning(e)

    def readXML(self, filename):
        return ET.parse(filename)

    def writeXML(self, etree, filename):
        # ET.indent(etree, space='    ')
        etree.write(filename, encoding="utf-8", xml_declaration=True)

    def XML2Dict(self, etree, namespaces=False):
        string = ET.tostring(etree.getroot())
        if(namespaces):
            return xmltodict.parse(string, process_namespaces=True)
        else:
            return xmltodict.parse(string)

    def Dict2XML(self, data):
        string = xmltodict.unparse(data, pretty=True)
        return ET.ElementTree(ET.fromstring(string))

    def XML2DictFromFile(self, filename, namespaces=False):
        etree = self.readXML(filename)
        return self.XML2Dict(etree, namespaces)

    def XML2ObjFromFile(self, filename, namespaces=False):
        etree = self.readXML(filename)
        edict = self.XML2Dict(etree, namespaces)
        return self.dict2obj(edict)

    def Dict2XMLToFile(self, data, filename):
        etree = ET.ElementTree(self.Dict2XML(data))
        etree.write(filename, encoding="utf-8", xml_declaration=True)
